/*-------------------------------------------------------------------------
 *
 * plannodes.h
 *	  查询计划节点的定义
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/nodes/plannodes.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef PLANNODES_H
#define PLANNODES_H

#include "access/sdir.h"
#include "access/stratnum.h"
#include "lib/stringinfo.h"
#include "nodes/bitmapset.h"
#include "nodes/lockoptions.h"
#include "nodes/parsenodes.h"
#include "nodes/primnodes.h"


/* ----------------------------------------------------------------
 *						节点定义
 * ----------------------------------------------------------------
 */

/* ----------------
 *		计划语句节点
 *
 * 规划器的输出是一个由 PlannedStmt 节点开头的计划树。
 * PlannedStmt 包含执行器所需的“一次性”信息。
 *
 * 为了简化 API，我们还将实用语句包装在 PlannedStmt 节点中；在这种情况下，commandType == CMD_UTILITY，语句本身位于 utilityStmt 字段中，其余结构大多是虚拟的。
 * （我们确实使用 canSetTag、stmt_location、stmt_len，以及可能的 queryId。）
 * ----------------
 */
typedef struct PlannedStmt
{
	NodeTag		type;

	CmdType		commandType;	/* select|insert|update|delete|merge|utility */

	uint64		queryId;		/* 查询标识符（从 Query 复制） */

	bool		hasReturning;	/* 是 insert|update|delete RETURNING 吗？ */

	bool		hasModifyingCTE;	/* 在 WITH 中包含 insert|update|delete 吗？ */

	bool		canSetTag;		/* 我是否设置命令结果标签？ */

	bool		transientPlan;	/* 当 TransactionXmin 变化时，是否重做计划？ */

	bool		dependsOnRole;	/* 计划是否特定于当前角色？ */

	bool		parallelModeNeeded; /* 执行需要并行模式吗？ */

	int			jitFlags;		/* 应该执行哪种形式的 JIT */

	struct Plan *planTree;		/* Plan 节点的树 */

	List	   *rtable;			/* RangeTblEntry 节点列表 */

	/* INSERT/UPDATE/DELETE/MERGE 的目标关系的 rtable 索引 */
	List	   *resultRelations;	/* RT 索引的整数列表，或 NIL */

	List	   *appendRelations;	/* AppendRelInfo 节点列表 */

	List	   *subplans;		/* SubPlan 表达式的计划树；注意
								 * 某些可能为 NULL */

	Bitmapset  *rewindPlanIDs;	/* 需要 REWIND 的子计划的索引 */

	List	   *rowMarks;		/* PlanRowMark 的列表 */

	List	   *relationOids;	/* 计划依赖的关系 OID */

	List	   *invalItems;		/* 其他依赖项，作为 PlanInvalItems */

	List	   *paramExecTypes; /* PARAM_EXEC 参数的类型 OID */

	Node	   *utilityStmt;	/* 如果这是一个实用语句则为非空 */

	/* 源字符串中的语句位置（从 Query 复制） */
	int			stmt_location;	/* 起始位置，如果未知则为-1 */
	int			stmt_len;		/* 字节长度；0表示“字符串其余部分” */
} PlannedStmt;

/* 用于获取与 SubPlan 节点相关的计划的宏 */
#define exec_subplan_get_plan(plannedstmt, subplan) \
	((Plan *) list_nth((plannedstmt)->subplans, (subplan)->plan_id - 1))


/* ----------------
 *		计划节点
 *
 * 所有计划节点“派生”自计划结构，方法是将计划结构作为第一个字段。这确保在将节点转换为 Plan 时一切正常。（节点指针在执行器中被通用地传递时经常被转换为 Plan*）
 *
 * 我们从未真正实例化任何计划节点；这只是所有 Plan 类型节点的通用抽象超类。
 * ----------------
 */
typedef struct Plan
{
	NodeTag		type;

	/*
	 * 计划的估计执行成本（更多信息请参见 costsize.c）
	 */
	Cost		startup_cost;	/* 获取任何元组之前的成本 */
	Cost		total_cost;		/* 总成本（假设获取所有元组） */

	/*
	 * 规划者对该计划步骤的结果大小的估计
	 */
	Cardinality plan_rows;		/* 计划预计发出的行数 */
	int			plan_width;		/* 每行的平均宽度（以字节为单位） */

	/*
	 * 并行查询所需的信息
	 */
	bool		parallel_aware; /* 启动并行感知逻辑？ */
	bool		parallel_safe;	/* 可作为并行计划的一部分使用吗？ */

	/*
	 * 异步执行所需的信息
	 */
	bool		async_capable;	/* 启用异步能力逻辑？ */

	/*
	 * 所有计划类型的公共结构数据。
	 */
	int			plan_node_id;	/* 在整个最终计划树中是唯一的 */
	List	   *targetlist;		/* 此节点要计算的目标列表 */
	List	   *qual;			/* 隐式 AND 条件 */
	struct Plan *lefttree;		/* 输入计划树(s) */
	struct Plan *righttree;
	List	   *initPlan;		/* 初始化计划节点（无相关表达式
								 * 子选择） */

	/*
	 * 用于管理参数变更驱动的重新扫描的信息
	 *
	 * extParam 包含所有外部 PARAM_EXEC 参数的 paramIDs
	 * 影响此计划节点或其子节点。来自节点的 initPlans 的 setParam 参数不包含在内，但它们的 extParams 包括在内。
	 *
	 * allParam 包含所有 extParam paramIDs，加上影响节点的本地
	 * 参数的 ID（即，其初始化计划的 setParams）。
	 * 这些是影响此节点的所有 PARAM_EXEC 参数。
	 */
	Bitmapset  *extParam;
	Bitmapset  *allParam;
} Plan;

/* ----------------
 *	这些被定义以避免 "left" 和 "right" 以及 "inner" 和 "outer"
 * 之间的混淆问题。惯例是 "left" 计划是 "outer" 计划，而 "right" 计划是
 * 内部计划，但这些使代码更具可读性。
 * ----------------
 */
#define innerPlan(node)			(((Plan *)(node))->righttree)
#define outerPlan(node)			(((Plan *)(node))->lefttree)


/* ----------------
 *	 结果节点 -
 *		如果没有外部计划，则评估一个无变量的目标列表。
 *		如果有外部计划，则从外部计划返回元组（经过一层
 *		如目标列表所示的投影）。
 *
 * 如果 resconstantqual 不是 NULL，它表示一次性资格
 * 测试（即，不依赖于外部计划中的任何变量，因此只需
 * 评估一次）。
 * ----------------
 */
typedef struct Result
{
	Plan		plan;
	Node	   *resconstantqual;
} Result;

/* ----------------
 *	 ProjectSet 节点 -
 *		对外部计划的输出元组应用包含集返回函数的投影。
 * ----------------
 */
typedef struct ProjectSet
{
	Plan		plan;
} ProjectSet;

/* ----------------
 *	 ModifyTable 节点 -
 *		通过插入、更新或删除将外部计划生成的行应用于结果表。
 *
 * 如果最初命名的目标表是一个分区表或继承树，
 * nominalRelation 和 rootRelation 都包含分区根或 appendrel RTE 的 RT 索引，
 * 在计划中没有其他提及。否则，rootRelation 为零。然而，nominalRelation 将
 * 始终被设置，因为它是 EXPLAIN 应该声称的
 * INSERT/UPDATE/DELETE/MERGE 目标。
 *
 * 注意，rowMarks 和 epqParam 被假定对所有表有效；它们不能包含在表之间变化的任何信息。
 * ----------------
 */
typedef struct ModifyTable
{
	Plan		plan;
	CmdType		operation;		/* INSERT、UPDATE、DELETE 或 MERGE */
	bool		canSetTag;		/* 我们设置命令标签/es_processed 吗？ */
	Index		nominalRelation;	/* 用于 EXPLAIN 的父 RT 索引 */
	Index		rootRelation;	/* 根 RT 索引，如果分区/继承的话 */
	bool		partColsUpdated;	/* 层次结构中的某个部分键已更新？ */
	List	   *resultRelations;	/* RT 索引的整数列表 */
	List	   *updateColnosLists;	/* 针对每个目标表的 update_colnos 列表 */
	List	   *withCheckOptionLists;	/* 针对每个目标表的 WCO 列表 */
	List	   *returningLists; /* 针对每个目标表的 RETURNING tlists */
	List	   *fdwPrivLists;	/* 每个目标表的 FDW 私有数据列表 */
	Bitmapset  *fdwDirectModifyPlans;	/* FDW DM 计划的索引 */
	List	   *rowMarks;		/* PlanRowMarks（仅限非锁定） */
	int			epqParam;		/* 为 EvalPlanQual 重新评估的 Param ID */
	OnConflictAction onConflictAction;	/* ON CONFLICT 操作 */
	List	   *arbiterIndexes; /* ON CONFLICT 裁决者索引 OIDs 列表 */
	List	   *onConflictSet;	/* INSERT ON CONFLICT DO UPDATE 目标列表 */
	List	   *onConflictCols; /* onConflictSet 的目标列编号 */
	Node	   *onConflictWhere;	/* ON CONFLICT UPDATE 的 WHERE */
	Index		exclRelRTI;		/* EXCLUDED 伪关系的 RTI */
	List	   *exclRelTlist;	/* EXCLUDED 伪关系的 tlist */
	List	   *mergeActionLists;	/* 针对每个目标表的 MERGE 动作列表 */
} ModifyTable;

struct PartitionPruneInfo;		/* 对下面结构的前向引用 */

/* ----------------
 *	 Append 节点 -
 *		生成子计划结果的连接。
 * ----------------
 */
typedef struct Append
{
	Plan		plan;
	Bitmapset  *apprelids;		/* 此节点形成的 appendrel(s) 的 RTI */
	List	   *appendplans;
	int			nasyncplans;	/* 异步计划的数量 */

	/*
	 * 所有在此索引之前的 'appendplans' 都是非部分计划。此索引之后的所有
	 * 'appendplans' 都是部分计划。
	 */
	int			first_partial_plan;

	/* 用于运行时子计划修剪的信息；如果不执行，则为 NULL */
	struct PartitionPruneInfo *part_prune_info;
} Append;

/* ----------------
 *	 MergeAppend 节点 -
 *		合并预排序子计划的结果以保留排序顺序。
 * ----------------
 */
typedef struct MergeAppend
{
	Plan		plan;
	Bitmapset  *apprelids;		/* 此节点形成的 appendrel(s) 的 RTI */
	List	   *mergeplans;
	/* 这些字段与结构 Sort 中的排序键信息相同： */
	int			numCols;		/* 排序键列的数量 */
	AttrNumber *sortColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *sortOperators;	/* 按照排序的操作符的 OID */
	Oid		   *collations;		/* 排序规则的OID */
	bool	   *nullsFirst;		/* NULLS FIRST/LAST方向 */
	/* 用于运行时子计划修剪的信息；如果不执行，则为 NULL */
	struct PartitionPruneInfo *part_prune_info;
} MergeAppend;

/* ----------------
 *	递归联合节点 -
 *		生成两个子计划的递归联合。
 *
 * “外部”子计划总是非递归项，而“内部”
 * 子计划是递归项。
 * ----------------
 */
typedef struct RecursiveUnion
{
	Plan		plan;
	int			wtParam;		/* 表示工作表的 Param ID */
	/* 在UNION ALL情况下剩余字段为零/空 */
	int			numCols;		/* 检查重复性的列数 */
	AttrNumber *dupColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *dupOperators;	/* 用于比较的相等运算符 */
	Oid		   *dupCollations;
	long		numGroups;		/* 输入中估计的组数 */
} RecursiveUnion;

/* ----------------
 *	位图与节点 -
 *		生成子计划结果的交集。
 *
 * 子计划必须是返回元组位图的类型。计划的targetlist
 * 和qual字段未使用，并且始终为NIL。
 * ----------------
 */
typedef struct BitmapAnd
{
	Plan		plan;
	List	   *bitmapplans;
} BitmapAnd;

/* ----------------
 *	位图或节点 -
 *		生成子计划结果的并集。
 *
 * 子计划必须是返回元组位图的类型。计划的targetlist
 * 和qual字段未使用，并且始终为NIL。
 * ----------------
 */
typedef struct BitmapOr
{
	Plan		plan;
	bool		isshared;
	List	   *bitmapplans;
} BitmapOr;

/*
 * ==========
 * 扫描节点
 * ==========
 */
typedef struct Scan
{
	Plan		plan;
	Index		scanrelid;		/* relid是范围表的索引 */
} Scan;

/* ----------------
 *		顺序扫描节点
 * ----------------
 */
typedef struct SeqScan
{
	Scan		scan;
} SeqScan;

/* ----------------
 *		表样本扫描节点
 * ----------------
 */
typedef struct SampleScan
{
	Scan		scan;
	/* 使用结构指针以避免在此处包含parsenodes.h */
	struct TableSampleClause *tablesample;
} SampleScan;

/* ----------------
 *		索引扫描节点
 *
 * indexqualorig是隐式与的索引条件表达式列表，每个
 * 表达式的形式与查询的WHERE条件中出现的相同。每个应
 * 为(indexkey OP comparisonval)或(comparisonval OP indexkey)的形式。
 * indexkey是引用索引基础表列的Var或表达式。comparisonval
 * 可以是任何表达式，但不会使用基础表的任何列。表达式按索引
 * 列位置排序（但引用同一索引列的项目可以以任何顺序出现）。
 * 只有在必须重新检查一个有损的indexqual时，runtime才使用indexqualorig。
 *
 * indexqual具有相同的形式，但如果有必要，表达式已被交换以
 * 将indexkeys放在左侧，而indexkeys则被识别索引列的Var节点替换
 * （它们的varno为INDEX_VAR，varattno为索引列号）。
 *
 * indexorderbyorig类似于任何通过索引实现的ORDER BY表达式的原始形式，
 * 而indexorderby则被修改为左侧有索引列Vars。在这里，多个表达式
 * 必须按精确的ORDER BY顺序出现，这不一定是索引列顺序。只提供
 * 表达式，而不提供ORDER BY SortGroupClauses中的辅助排序信息；
 * 假定排序顺序可以从顶层操作符完全确定。如果索引无法计算准确的排序，
 * indexorderbyorig在runtime中用于重新检查排序。它还用于EXPLAIN。
 *
 * indexorderbyops是用于对ORDER BY表达式进行排序的运算符OID列表。
 * 这与indexorderbyorig一起用于runtime中重新检查排序。 （注意，indexorderby，
 * indexorderbyorig和indexorderbyops用于amcanorderbyop情况，而不是amcanorder。）
 *
 * indexorderdir指定扫描排序，适用于amcanorder索引上的indexscans
 * （对其他索引则应为“不管”）。
 * ----------------
 */
typedef struct IndexScan
{
	Scan		scan;
	Oid			indexid;		/* 要扫描的索引的OID */
	List	   *indexqual;		/* 索引条件的列表（通常是OpExprs） */
	List	   *indexqualorig;	/* 原始形式相同 */
	List	   *indexorderby;	/* 索引ORDER BY表达式的列表 */
	List	   *indexorderbyorig;	/* 原始形式相同 */
	List	   *indexorderbyops;	/* ORDER BY表达式的排序运算符OID */
	ScanDirection indexorderdir;	/* 向前或向后或不管 */
} IndexScan;


/* ----------------
 *		index-only scan node
 *
 * IndexOnlyScan 与 IndexScan 非常相似，但它指定了索引仅扫描，其中数据来自索引而不是堆。
 * 因此，计划节点的目标列表、条件和索引表达式中的 *所有* Vars 引用索引列，并且 varno = INDEX_VAR。
 *
 * 在重新检查有损索引操作符时，我们几乎可以直接将 indexqual 用于索引的输出元组，
 * 但这对不可检索的索引列的条件无效。因此，需要 recheckqual 进行重新检查：
 * 它表达与 indexqual 相同的条件，但仅使用可检索的索引列。
 * （如果这不可能，我们不会生成索引仅扫描。一个例子是，如果一个
 * 索引在可检索索引列 "ind1" 中具有表列 "x"，加上在不可检索列 "ind2" 中的表达式 f(x)，
 * 对 f(x) 的可索引查询将在 indexqual 中使用 "ind2"，并在 recheckqual 中使用 f(ind1)。
 * 如果没有 "ind1" 列，将不允许索引仅扫描。）
 *
 * 我们目前不需要 indexorderby 的可重新检查等效项，
 * 因为我们不支持索引 ORDER BY 中的有损操作符。
 *
 * 为了帮助 EXPLAIN 解读显示的索引 Vars，我们提供
 * indextlist，它表示索引的内容作为目标列表，每个索引列一个 TLE。
 * 出现在此列表中的 Vars 引用基础表，这是计划节点中可能包含此类 Vars 的唯一字段。
 * 此外，出于 setrefs.c 的方便，indextlist 中的 TLE 如果对应于索引 AM 无法重建的列则标记为 resjunk。
 * ----------------
 */
typedef struct IndexOnlyScan
{
	Scan		scan;
	Oid			indexid;		/* 要扫描的索引的OID */
	List	   *indexqual;		/* 索引条件的列表（通常是OpExprs） */
	List	   *recheckqual;	/* index quals in recheckable form */
	List	   *indexorderby;	/* 索引ORDER BY表达式的列表 */
	List	   *indextlist;		/* TargetEntry list describing index's cols */
	ScanDirection indexorderdir;	/* 向前或向后或不管 */
} IndexOnlyScan;

/* ----------------
 *		bitmap index scan node
 *
 * BitmapIndexScan 提供潜在元组位置的位图；
 * 它不直接访问堆。位图由一个祖先 BitmapHeapScan 节点使用，可能会经过
 * 中间的 BitmapAnd 和/或 BitmapOr 节点将其与其他 BitmapIndexScan 的结果组合。
 *
 * 这些字段的意义与 IndexScan 相同，除了我们不存储方向标志，因为方向不重要。
 *
 * 在 BitmapIndexScan 计划节点中，目标列表和条件字段不使用，始终为 NIL。
 * indexqualorig 字段在运行时也未使用，但为 EXPLAIN 保存。
 * ----------------
 */
typedef struct BitmapIndexScan
{
	Scan		scan;
	Oid			indexid;		/* 要扫描的索引的OID */
	bool		isshared;		/* Create shared bitmap if set */
	List	   *indexqual;		/* 索引条件的列表 (OpExprs) */
	List	   *indexqualorig;	/* 原始形式相同 */
} BitmapIndexScan;

/* ----------------
 *		位图顺序扫描节点
 *
 * 这需要输入索引扫描所使用的 qual 条件的副本，因为在各种情况下，我们需要重新检查 quals；
 * 例如，当位图关于满足索引条件的页面上的特定行是有损的。
 * ----------------
 */
typedef struct BitmapHeapScan
{
	Scan		scan;
	List	   *bitmapqualorig; /* 索引 quals，以标准表达式形式 */
} BitmapHeapScan;

/* ----------------
 *		tid 扫描节点
 *
 * tidquals 是一种隐式 OR 连接的 qual 表达式列表，形式为
 * "CTID = pseudoconstant"，或 "CTID = ANY(pseudoconstant_array)",
 * 或关系的 CurrentOfExpr。
 * ----------------
 */
typedef struct TidScan
{
	Scan		scan;
	List	   *tidquals;		/* 涉及 CTID = 某个东西的 qual(s) */
} TidScan;

/* ----------------
 *		tid 范围扫描节点
 *
 * tidrangequals 是一种隐式 AND 连接的 qual 表达式列表，形式为
 * "CTID relop pseudoconstant"，其中 relop 是 >,>=,<,<= 之一。
 * ----------------
 */
typedef struct TidRangeScan
{
	Scan		scan;
	List	   *tidrangequals;	/* 包含 CTID op 某个东西的 qual(s) */
} TidRangeScan;

/* ----------------
 *		子查询扫描节点
 *
 * SubqueryScan 用于扫描范围表中子查询的输出。
 * 我们经常需要在子查询的计划上方添加一个额外的计划节点来执行
 * 表达式评估（我们不能在不冒险改变其语义的情况下将其推入子查询）。虽然我们并没有扫描物理
 * 关系，但我们仍然使其成为 Scan 的子类，以便于代码共享。
 *
 * SubqueryScanStatus 缓存节点的 trivial_subqueryscan 属性。
 * SUBQUERY_SCAN_UNKNOWN 表示尚未确定。这仅在
 * 规划期间使用。
 *
 * 注意：我们将子计划存储在特定类型的子计划字段中，而不是您可能期望的通用 lefttree 字段中。这是因为我们不希望计划树遍历例程在
 * 不确认它们正在更改查询上下文的情况下递归进入子计划。
 * ----------------
 */
typedef enum SubqueryScanStatus
{
	SUBQUERY_SCAN_UNKNOWN,
	SUBQUERY_SCAN_TRIVIAL,
	SUBQUERY_SCAN_NONTRIVIAL
} SubqueryScanStatus;

typedef struct SubqueryScan
{
	Scan		scan;
	Plan	   *subplan;
	SubqueryScanStatus scanstatus;
} SubqueryScan;

/* ----------------
 *		函数扫描节点
 * ----------------
 */
typedef struct FunctionScan
{
	Scan		scan;
	List	   *functions;		/* RangeTblFunction 节点的列表 */
	bool		funcordinality; /* 带序号 */
} FunctionScan;

/* ----------------
 *		值扫描节点
 * ----------------
 */
typedef struct ValuesScan
{
	Scan		scan;
	List	   *values_lists;	/* 表达式列表的列表 */
} ValuesScan;

/* ----------------
 *		表函数扫描节点
 * ----------------
 */
typedef struct TableFuncScan
{
	Scan		scan;
	TableFunc  *tablefunc;		/* 表函数节点 */
} TableFuncScan;

/* ----------------
 *		Cte 扫描节点
 * ----------------
 */
typedef struct CteScan
{
	Scan		scan;
	int			ctePlanId;		/* CTE 的初始化 SubPlan 的 ID */
	int			cteParam;		/* 表示 CTE 输出的参数 ID */
} CteScan;

/* ----------------
 *		命名元组存储扫描节点
 * ----------------
 */
typedef struct NamedTuplestoreScan
{
	Scan		scan;
	char	   *enrname;		/* 给予临时命名关系的名称 */
} NamedTuplestoreScan;

/* ----------------
 *		工作表扫描节点
 * ----------------
 */
typedef struct WorkTableScan
{
	Scan		scan;
	int			wtParam;		/* 表示工作表的 Param ID */
} WorkTableScan;

/* ----------------
 *		国外扫描节点
 *
 * fdw_exprs 和 fdw_private 都在外部数据包装器的控制下，但 fdw_exprs 被假定为包含表达式树，并将由规划器相应地进行后处理；fdw_private 则不会。
 * 注意，两个列表中的所有内容都必须可以通过 copyObject() 复制。
 * 存储任意字节 blob 的一种方式是将其表示为 bytea Const。通常，您最好选择一种可以通过 nodeToString() 有效转储的表示。
 *
 * fdw_scan_tlist 是描述由 FDW 返回的扫描元组内容的目标列表；如果扫描元组与外部表的声明行类型匹配，则可以是 NIL，这对于简单的外部表扫描是正常的情况。
 * （如果计划节点表示外部连接，则需要 fdw_scan_tlist，因为系统目录中没有可用的行类型。）
 * 当提供 fdw_scan_tlist 时，节点的 tlist 和 quals 中的 Vars 必须具有 varno INDEX_VAR，并且它们的 varattnos 对应于 fdw_scan_tlist 中的 resnos（这些也是实际扫描元组中的列号）。
 * fdw_scan_tlist 实际上从未被执行；它仅仅包含描述扫描元组列中的内容的表达式树。
 *
 * fdw_recheck_quals 应该包含核心系统传递给 FDW 但未添加到 scan.plan.qual 的任何 quals；也就是说，它应该包含正在远程检查的 quals。这对于在 EvalPlanQual 重新检查期间的正确行为是必要的。
 *
 * 当计划节点表示外部连接时，scan.scanrelid 为零，必须咨询 fs_relids 以识别连接关系。（fs_relids 也适用于简单扫描，但始终会与 scan.scanrelid 匹配。）
 *
 * 如果 FDW 的 PlanDirectModify() 回调决定重新使用 ForeignScan 节点在远程服务器中直接执行 UPDATE 或 DELETE 操作，它将设置“operation”和“resultRelation”以识别操作
 * 类型和目标关系。注意，这些字段仅在完全远程执行修改时设置；否则，修改由本地 ModifyTable 节点驱动，“operation”保持 CMD_SELECT。
 * ----------------
 */
typedef struct ForeignScan
{
	Scan		scan;
	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
	Index		resultRelation; /* 直接修改目标的 RT 索引 */
	Oid			fs_server;		/* 外部服务器的 OID */
	List	   *fdw_exprs;		/* FDW 可能评估的表达式 */
	List	   *fdw_private;	/* FDW 的私有数据 */
	List	   *fdw_scan_tlist; /* 描述扫描元组的可选 tlist */
	List	   *fdw_recheck_quals;	/* 不在 scan.plan.qual 中的原始条件 */
	Bitmapset  *fs_relids;		/* 由此扫描生成的 RTI */
	bool		fsSystemCol;	/* 如果需要任何“系统列”，则为真 */
} ForeignScan;

/* ----------------
 *	   CustomScan 节点
 *
 * ForeignScan 的 fdw_exprs、fdw_private、fdw_scan_tlist 和 fs_relids
 * 字段的注释同样适用于 CustomScan 的 custom_exprs、custom_private、
 * custom_scan_tlist 和 custom_relids 字段。对连接而言，将
 * scan.scanrelid 设置为零的约定也适用。
 *
 * 请注意，由于 Plan 树可以被复制，自定义扫描提供者 *必须*
 * 将他们所需的所有计划数据放入这些字段中；将 CustomScan
 * 嵌入到更大的结构中将不起作用。
 * ----------------
 */
struct CustomScanMethods;

typedef struct CustomScan
{
	Scan		scan;
	uint32		flags;			/* CUSTOMPATH_* 标志的掩码，见
								 * nodes/extensible.h */
	List	   *custom_plans;	/* 计划节点的列表（如果有） */
	List	   *custom_exprs;	/* 自定义代码可能评估的表达式 */
	List	   *custom_private; /* 自定义代码的私有数据 */
	List	   *custom_scan_tlist;	/* 描述扫描元组的可选 tlist */
	Bitmapset  *custom_relids;	/* 由此扫描生成的 RTI */
	const struct CustomScanMethods *methods;
} CustomScan;

/*
 * ==========
 * 连接节点
 * ==========
 */

/* ----------------
 *		连接节点
 *
 * jointype:	连接左子树和右子树的规则
 * inner_unique 每个外部元组最多只能匹配到一个内部元组
 * joinqual:	来自 JOIN/ON 或 JOIN/USING 的条件
 *				(plan.qual 包含来自 WHERE 的条件)
 *
 * 当 jointype 为 INNER 时，joinqual 和 plan.qual 在语义上是
 * 可互换的。对于 OUTER jointypes，两者*不可*互换；
 * 只有 joinqual 被用来决定是否找到了匹配，以便决定是否生成
 * null-扩展元组。
 * （但在实际返回元组之前，plan.qual 仍然被应用。）
 * 对于外连接，仅允许使用 joinquals 作为合并或哈希连接的合并
 * 或哈希条件。
 *
 * 如果 joinquals 的条件是这样的，即至多只有一个内部元组
 * 可以匹配任何给定的外部元组，则 inner_unique 被设置。
 * 这使得执行器可以跳过额外匹配的搜索。
 * （这必须能够仅根据 joinquals 证明，而忽略 plan.qual，
 * 这是由于执行器进行测试的位置。）
 * ----------------
 */
typedef struct Join
{
	Plan		plan;
	JoinType	jointype;
	bool		inner_unique;
	List	   *joinqual;		/* JOIN 资格（除了 plan.qual） */
} Join;

/* ----------------
 *		嵌套循环连接节点
 *
 * nestParams 列表识别必须传递到执行内部子计划的任何执行器参数，
 * 以便携带当前外部子计划行的值。目前，我们限制这些值为
 * 简单的 Vars，但或许有一天可以放宽这一限制。
 * （注意：在计划创建期间，paramval 实际上可以是 PlaceHolderVar
 * 表达式；但到达执行器时，它必须是 varno 为 OUTER_VAR 的 Var。）
 * ----------------
 */
typedef struct NestLoop
{
	Join		join;
	List	   *nestParams;		/* NestLoopParam 节点列表 */
} NestLoop;

typedef struct NestLoopParam
{
	NodeTag		type;
	int			paramno;		/* 要设置的 PARAM_EXEC 参数的编号 */
	Var		   *paramval;		/* 要分配给 Param 的外部关系 Var */
} NestLoopParam;

/* ----------------
 *		合并连接节点
 *
 * 每个可合并列的预期排序由 btree opfamily OID、一个 collation OID、
 * 一个方向（BTLessStrategyNumber 或 BTGreaterStrategyNumber）
 * 和一个 nulls-first 标志描述。注意，每个 mergeclause 的
 * 两侧可能具有不同的数据类型，但根据共同的 opfamily 和 collation
 * 进行相同的排序。每个 mergeclause 中的运算符必须是所指示
 * 的 opfamily 的相等运算符。
 * ----------------
 */
typedef struct MergeJoin
{
	Join		join;
	bool		skip_mark_restore;	/* 我们可以跳过标记/恢复调用吗？ */
	List	   *mergeclauses;	/* mergeclauses 作为表达式树 */
	/* 这些是数组，但与mergeclauses列表的长度相同： */
	Oid		   *mergeFamilies;	/* 每个子句的btree操作族OID */
	Oid		   *mergeCollations;	/* 每个子句的排序规则OID */
	int		   *mergeStrategies;	/* 每个子句的排序（ASC或DESC） */
	bool	   *mergeNullsFirst;	/* 每个子句的 null 顺序 */
} MergeJoin;

/* ----------------
 *		哈希连接节点
 * ----------------
 */
typedef struct HashJoin
{
	Join		join;
	List	   *hashclauses;
	List	   *hashoperators;
	List	   *hashcollations;

	/*
	 * 要为来自外部计划的元组哈希的表达式列表，
	 * 以便在内部计划的哈希表中执行查找。
	 */
	List	   *hashkeys;
} HashJoin;

/* ----------------
 *		物化节点
 * ----------------
 */
typedef struct Material
{
	Plan		plan;
} Material;

/* ----------------
 *		记忆化节点
 * ----------------
 */
typedef struct Memoize
{
	Plan		plan;

	int			numKeys;		/* 以下两个数组的大小 */

	Oid		   *hashOperators;	/* 每个键的哈希运算符 */
	Oid		   *collations;		/* 每个键的 collation */
	List	   *param_exprs;	/* 以包含参数的 exprs 形式缓存的键 */
	bool		singlerow;		/* 如果在存储第一个元组后，缓存条目应标记为
								 * 完成，则为真。 */
	bool		binary_mode;	/* 当缓存键应逐位比较时为真，使用哈希相等操作时为假 */
	uint32		est_entries;	/* 规划者预计将适合缓存的最大条目数，或如果未知则为0 */
	Bitmapset  *keyparamids;	/* 来自 param_exprs 的 paramids */
} Memoize;

/* ----------------
 *		排序节点
 * ----------------
 */
typedef struct Sort
{
	Plan		plan;
	int			numCols;		/* 排序键列的数量 */
	AttrNumber *sortColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *sortOperators;	/* 按照排序的操作符的 OID */
	Oid		   *collations;		/* 排序规则的OID */
	bool	   *nullsFirst;		/* NULLS FIRST/LAST方向 */
} Sort;

/* ----------------
 *		增量排序节点
 * ----------------
 */
typedef struct IncrementalSort
{
	Sort		sort;
	int			nPresortedCols; /* 预排序列的数量 */
} IncrementalSort;

/* ---------------
 *	 分组节点 -
 *		用于指定 GROUP BY（但没有聚合）的查询。
 *		输入必须根据分组列进行预排序。
 * ---------------
 */
typedef struct Group
{
	Plan		plan;
	int			numCols;		/* 分组列的数量 */
	AttrNumber *grpColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *grpOperators;	/* 用于比较的相等运算符 */
	Oid		   *grpCollations;
} Group;

/* ---------------
 *		聚合节点
 *
 * Agg 节点实现普通或分组聚合。对于分组聚合，我们可以在
 * 预排序输入或未排序输入上工作；后者策略使用内部哈希表。
 *
 * 注意没有关于要计算的聚合函数的任何直接信息。
 * 它们在执行器启动期间通过扫描节点的 tlist 和 quals 找到。
 * （可能没有聚合函数；如果它们通过常量折叠被优化掉，或者
 * 如果我们使用 Agg 节点实现基于哈希的分组，就会发生这种情况。）
 * ---------------
 */
typedef struct Agg
{
	Plan		plan;
	AggStrategy aggstrategy;	/* 基本策略，见 nodes.h */
	AggSplit	aggsplit;		/* agg-拆分模式，见 nodes.h */
	int			numCols;		/* 分组列的数量 */
	AttrNumber *grpColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *grpOperators;	/* 用于比较的相等运算符 */
	Oid		   *grpCollations;
	long		numGroups;		/* 输入中估计的组数 */
	uint64		transitionSpace;	/* 通过引用传递的过渡数据 */
	Bitmapset  *aggParams;		/* 在 Aggref 输入中使用的参数的 IDs */
	/* 注意：规划器仅在 HASHED/MIXED 情况下提供 numGroups & aggParams */
	List	   *groupingSets;	/* 要使用的分组集合 */
	List	   *chain;			/* 链接的 Agg/Sort 节点 */
} Agg;

/* ----------------
 *		窗口聚合节点
 * ----------------
 */
typedef struct WindowAgg
{
	Plan		plan;
	Index		winref;			/* 窗口函数引用的 ID */
	int			partNumCols;	/* 分区子句中的列数 */
	AttrNumber *partColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *partOperators;	/* 分区列的等值运算符 */
	Oid		   *partCollations; /* 分区列的排序规则 */
	int			ordNumCols;		/* 排序子句中的列数 */
	AttrNumber *ordColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *ordOperators;	/* 排序列的等值运算符 */
	Oid		   *ordCollations;	/* 排序列的排序规则 */
	int			frameOptions;	/* frame_clause选项，参见WindowDef */
	Node	   *startOffset;	/* 起始界限的表达式（如果有） */
	Node	   *endOffset;		/* 结束界限的表达式（如果有） */
	List	   *runCondition;	/* qual 以帮助短路执行 */
	List	   *runConditionOrig;	/* 在 EXPLAIN 中显示的 runCondition */
	/* 这些字段用于RANGE偏移PRECEDING/FOLLOWING： */
	Oid			startInRangeFunc;	/* startOffset的in_range函数 */
	Oid			endInRangeFunc; /* endOffset的in_range函数 */
	Oid			inRangeColl;	/* in_range测试的排序规则 */
	bool		inRangeAsc;		/* 使用 ASC 排序顺序进行范围测试？ */
	bool		inRangeNullsFirst;	/* 空值在范围测试中优先排序吗？ */
	bool		topWindow;		/* 除了离计划根部最近的 WindowAgg 外，其他全部为 false */
} WindowAgg;

/* ----------------
 *		唯一节点
 * ----------------
 */
typedef struct Unique
{
	Plan		plan;
	int			numCols;		/* 检查唯一性的列数 */
	AttrNumber *uniqColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *uniqOperators;	/* 用于比较的相等运算符 */
	Oid		   *uniqCollations; /* 等值比较的排序规则 */
} Unique;

/* ------------
 *		聚合节点
 *
 * 注意：rescan_param 是 PARAM_EXEC 参数槽的 ID。该槽
 * 实际上永远不会包含值，但聚合节点必须在重新扫描时标记
 * 它已更改。子并行感知扫描
 * 节点被标记为依赖于该参数，因此重新扫描
 * 机制意识到它们的输出可能会在重新扫描中发生变化。
 * 在某些情况下，我们不需要重新扫描参数，因此 rescan_param 被设置为 -1。
 * ------------
 */
typedef struct Gather
{
	Plan		plan;
	int			num_workers;	/* 计划的工作进程数 */
	int			rescan_param;	/* 表示重新扫描的参数 ID，或 -1 */
	bool		single_copy;	/* 不要执行计划超过一次 */
	bool		invisible;		/* 抑制 EXPLAIN 显示（用于测试）? */
	Bitmapset  *initParam;		/* 在聚合或其子节点中引用的 initplans 的参数 ID */
} Gather;

/* ------------
 *		聚合合并节点
 * ------------
 */
typedef struct GatherMerge
{
	Plan		plan;
	int			num_workers;	/* 计划的工作进程数 */
	int			rescan_param;	/* 表示重新扫描的参数 ID，或 -1 */
	/* 其余字段与结构 Sort 中的排序键信息相同 */
	int			numCols;		/* 排序键列的数量 */
	AttrNumber *sortColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *sortOperators;	/* 按照排序的操作符的 OID */
	Oid		   *collations;		/* 排序规则的OID */
	bool	   *nullsFirst;		/* NULLS FIRST/LAST方向 */
	Bitmapset  *initParam;		/* 在聚合合并或其子节点中引用的 initplans 的参数 ID */
} GatherMerge;

/* ----------------
 *		哈希构建节点
 *
 * 如果执行器应该尝试应用偏斜连接优化，则
 * skewTable/skewColumn/skewInherit 标识外部关系的连接键
 * 列，从中可以获取相关的 MCV 统计信息。
 * ----------------
 */
typedef struct Hash
{
	Plan		plan;

	/*
	 * 要为来自 Hash 的外部计划的元组哈希的表达式列表，
	 * 需要将其放入哈希表中。
	 */
	List	   *hashkeys;		/* 哈希连接条件的哈希键 */
	Oid			skewTable;		/* 外连接键的表 OID，或 InvalidOid */
	AttrNumber	skewColumn;		/* 外连接键的列编号，或零 */
	bool		skewInherit;	/* 外连接关系是继承树吗？ */
	/* 所有其他信息在父 HashJoin 节点中 */
	Cardinality rows_total;		/* 如果并行感知，估算总行数 */
} Hash;

/* ----------------
 *		集合操作节点
 * ----------------
 */
typedef struct SetOp
{
	Plan		plan;
	SetOpCmd	cmd;			/* 应该做什么，参见 nodes.h */
	SetOpStrategy strategy;		/* 如何做到这一点，参见 nodes.h */
	int			numCols;		/* 检查重复性的列数 */
	AttrNumber *dupColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *dupOperators;	/* 用于比较的相等运算符 */
	Oid		   *dupCollations;
	AttrNumber	flagColIdx;		/* 标志列的位置，如果有的话 */
	int			firstFlag;		/* 第一个输入关系的标志值 */
	long		numGroups;		/* 输入中估计的组数 */
} SetOp;

/* ----------------
 *		锁行节点
 *
 * rowMarks 标识此节点要锁定的关系；它应该是
 * 顶层 PlannedStmt 中列出的 rowMarks 的子集。
 * epqParam 是所有位于该节点下方的扫描节点必须依赖的参数。
 * 它用于在 EvalPlanQual 期间强制重新评估计划。
 * ----------------
 */
typedef struct LockRows
{
	Plan		plan;
	List	   *rowMarks;		/* PlanRowMark 的列表 */
	int			epqParam;		/* 为 EvalPlanQual 重新评估的 Param ID */
} LockRows;

/* ----------------
 *		限制节点
 *
 * 注：自 Postgres 8.2 起，偏移量和计数表达式预计将返回 int8，而不是之前的 int4。
 * ----------------
 */
typedef struct Limit
{
	Plan		plan;
	Node	   *limitOffset;	/* OFFSET 参数，或者如果没有则为 NULL */
	Node	   *limitCount;		/* COUNT 参数，或者如果没有则为 NULL */
	LimitOption limitOption;	/* 限制类型 */
	int			uniqNumCols;	/* 检查相似性的列数 */
	AttrNumber *uniqColIdx;		/* 它们在目标列表中的索引 */
	Oid		   *uniqOperators;	/* 用于比较的相等运算符 */
	Oid		   *uniqCollations; /* 等值比较的排序规则 */
} Limit;


/*
 * RowMarkType -
 *	  行标记操作类型的枚举
 *
 * 这四个值表示根据 SELECT FOR [KEY] UPDATE/SHARE 请求可针对元组采取的不同锁强度。
 * 我们支持这些在常规表上，以及在其 FDW 报告支持晚期锁定的外部表上。对于其他外部表，
 * 任何可能为此类请求完成的锁定必须在初始行获取时发生；它们的 FDW 不提供返回后锁定行的机制。
 * 这意味着语义将与本地表略有不同；特别是我们可能会锁定比本地锁定更多的行，因为即使远程行
 * 后来失败于本地检查的限制或连接条件，它们也会被锁定。然而，为每个选定的行执行单独的远程查询
 * 以进行锁定的前景通常是相当不吸引的，因此早期锁定仍然是 FDW 的合理设计选择。
 *
 * 在执行 UPDATE/DELETE/MERGE/SELECT FOR UPDATE/SHARE 时，我们必须唯一标识所有源行，
 * 不仅是目标关系中的那些，以便我们能在需要时进行 EvalPlanQual 重新检查。对于普通表，我们
 * 可以简单地获取 TID，类似于目标关系；该情况由 ROW_MARK_REFERENCE 表示。否则（例如，对于 VALUES
 * 或 FUNCTION 扫描），我们必须复制整个行值。ROW_MARK_COPY 的效率相当低，因为大多数情况下我们
 * 不会需要数据；但幸运的是，实际上开销通常不会影响性能。在外部表中，我们默认使用 ROW_MARK_COPY，
 * 但如果 FDW 有行 ID 的概念，它可以请求使用 ROW_MARK_REFERENCE。
 * （再说一次，如果需要物理远程获取，这可能没有意义，但对于映射到本地存储的 FDW，这可能是可信的。）
 */
typedef enum RowMarkType
{
	ROW_MARK_EXCLUSIVE,			/* 获取独占元组锁 */
	ROW_MARK_NOKEYEXCLUSIVE,	/* 获取无键独占元组锁 */
	ROW_MARK_SHARE,				/* 获取共享元组锁 */
	ROW_MARK_KEYSHARE,			/* 获取键共享元组锁 */
	ROW_MARK_REFERENCE,			/* 只获取 TID，不锁定它 */
	ROW_MARK_COPY				/* 物理复制行值 */
} RowMarkType;

#define RowMarkRequiresRowShareLock(marktype)  ((marktype) <= ROW_MARK_KEYSHARE)


/*
 * PlanRowMark -
 *	   plan-time representation of FOR [KEY] UPDATE/SHARE clauses
 *
 * 当执行 UPDATE/DELETE/MERGE/SELECT FOR UPDATE/SHARE 时，我们为查询中的每个非目标关系创建一个单独的 PlanRowMark 节点。未被指定为 FOR UPDATE/SHARE 的关系标记为 ROW_MARK_REFERENCE（如果是常规表或支持的外部表）或 ROW_MARK_COPY（如果不是）。
 *
 * 最初，所有 PlanRowMark 的 rti == prti 且 isParent == false。当规划者发现某个关系是继承树的根时，它将 isParent 设置为 true，并为每个子关系（如果目标关系不是分区表，还包括其作为子关系的角色）添加一个额外的 PlanRowMark 进入列表。任何非叶子分区子关系也将具有 isParent = true 的条目。子条目具有 rti == 子关系的 RT 索引和 prti == 顶级父关系的 RT 索引，因此可以通过 prti != rti 来识别为子关系。父关系的 allMarkTypes 字段获取其所有子关系的 (1<<markType) 的 OR（此定义允许子关系使用不同的 markTypes）。
 *
 * 规划者还向计划中添加 resjunk 输出列，以携带足够的信息来识别锁定或获取的行。当 markType != ROW_MARK_COPY 时，这些列命名为
 *		tableoid%u			表的 OID
 *		ctid%u				行的 TID
 * tableoid 列仅在继承层次结构中存在。当 markType == ROW_MARK_COPY 时，而是有一个名为
 *		wholerow%u			关系的整行值的单列
 * （如果某些子关系使用与其他子关系不同的 markType，则一个继承层次结构可以拥有所有三个 resjunk 输出列。）
 * 在所有三种情况下，%u 代表 rowmark ID 号（rowmarkId）。此号码在计划树中是唯一的，除了子关系条目复制其父关系的 rowmarkId。（分配唯一号码意味着在扁平化子查询时我们不需要重新编号 rowmarkIds，这将需要查找和重命名 resjunk 列。）
 * 请注意，这意味着继承层次结构中的所有表共享相同的 resjunk 列名。
 */
typedef struct PlanRowMark
{
	NodeTag		type;
	Index		rti;			/* 可标记关系的范围表索引 */
	Index		prti;			/* 父关系的范围表索引 */
	Index		rowmarkId;		/* 对于 resjunk 列的唯一标识符 */
	RowMarkType markType;		/* 参见上面的枚举 */
	int			allMarkTypes;	/* 所有子项的 (1<<markType) 的 OR */
	LockClauseStrength strength;	/* LockingClause 的强度，或 LCS_NONE */
	LockWaitPolicy waitPolicy;	/* NOWAIT 和 SKIP LOCKED 选项 */
	bool		isParent;		/* 如果这是一个“虚拟”父条目，则为 true */
} PlanRowMark;


/*
 * 节点类型以表示分区修剪信息。
 */

/*
 * PartitionPruneInfo - 允许执行器修剪分区所需的详细信息。
 *
 * 在这里，我们存储映射细节，以允许将分区表的索引（由分区修剪代码返回）转换为支持任意数量子计划的计划类型的子计划索引，例如 Append。
 * 我们还存储各种细节以告诉执行器何时应执行分区修剪。
 *
 * 每个 PartitionedRelPruneInfo 描述单个分区表的分区规则（又名分区水平）。由于一个分区层次结构可能包含多个层次，我们通过一个 PartitionedRelPruneInfos 的列表来表示它，其中第一个条目表示最上层的分区表，附加条目表示非叶子子分区，按父项在子项之前的顺序排列。然后，由于 Append 类型的节点可以在其子项中具有多个分区层次结构，因此我们有一个无序的列表来包含这些列表。
 *
 * prune_infos			包含 PartitionedRelPruneInfo 节点的列表列表，
 *						每个运行时可修剪分区层次结构对应一个子列表
 *						出现在父计划节点的子计划中。
 * other_subplans		任何未被“prune_infos”中的任何 PartitionedRelPruneInfo 节点所计算的子计划的索引。
 *						这些子计划不能被修剪。
 */
typedef struct PartitionPruneInfo
{
	NodeTag		type;
	List	   *prune_infos;
	Bitmapset  *other_subplans;
} PartitionPruneInfo;


/*
 * PartitionedRelPruneInfo - 允许执行器修剪单个分区表的分区所需的详细信息。
 *
 * subplan_map[] 和 subpart_map[] 由 'rtindex' 引用的分区表的分区索引索引，分区索引是分区在表的 PartitionDesc 中定义的顺序。对于叶子分区 p，subplan_map[p] 包含该分区在父计划的子计划列表中的零基索引；如果分区是非叶子分区或已被修剪，则为 -1。对于非叶子分区 p，subpart_map[p] 包含该子分区的 PartitionedRelPruneInfo 在层次结构的 PartitionedRelPruneInfo 列表中的零基索引；如果分区是叶子分区或已被修剪，则为 -1。请注意，存储在 'subplan_map' 中的子计划索引在父计划节点中是全局的，但分区索引仅在特定层次结构内有效。relid_map[p] 包含该分区的 OID，如果该分区已被修剪，则为 0。
 */
typedef struct PartitionedRelPruneInfo
{
	NodeTag		type;
	Index		rtindex;		/* 该级别的分区关系的 RT 索引 */
	Bitmapset  *present_parts;	/* 包含子计划或子部分存在的所有分区的索引 */
	int			nparts;			/* 以下数组的长度： */
	int		   *subplan_map;	/* 按分区索引的子计划索引，或 -1 */
	int		   *subpart_map;	/* 按分区索引的子分区索引，或 -1 */
	Oid		   *relid_map;		/* 按分区索引的关系 OID，或 0 */

	/*
	 * initial_pruning_steps 表示在执行器启动期间如何进行修剪（即，
	 * 不使用任何 PARAM_EXEC 参数）；如果不需要启动修剪则为 NIL。exec_pruning_steps
	 * 表示如何使用 PARAM_EXEC 参数进行修剪；如果不需要每个扫描的修剪则为 NIL。
	 */
	List	   *initial_pruning_steps;	/* PartitionPruneStep 列表 */
	List	   *exec_pruning_steps; /* PartitionPruneStep 列表 */
	Bitmapset  *execparamids;	/* exec_pruning_steps 中所有的 PARAM_EXEC 参数 ID */
} PartitionedRelPruneInfo;

/*
 * 用于分区修剪步骤的抽象节点类型（没有此类型的具体节点）。
 *
 * step_id 是该步骤在其修剪上下文中的全局标识符。
 */
typedef struct PartitionPruneStep
{
	NodeTag		type;
	int			step_id;
} PartitionPruneStep;

/*
 * PartitionPruneStepOp - 使用一组互相 AND 的 OpExpr 子句进行修剪的信息
 *
 * 这包含从最多 partnatts 个 OpExpr 子句中提取的信息，其中 partnatts 是分区键列的数量。
 * 'opstrategy' 是与最后一个分区键匹配的子句中运算符的策略。'exprs' 包含包含用于传递给
 * 分区边界搜索函数的查找键的表达式。'cmpfns' 包含用于将上述表达式与
 * 分区边界进行比较的比较函数的 OID。'exprs' 和 'cmpfns' 包含相同数量的项目，最多为 partnatts 项。
 *
 * 一旦我们找到使用查找键的分区边界的偏移量，我们就会根据 'opstrategy' 的值
 * 确定应包含哪些分区在结果中。例如，如果是相等，我们将仅返回包含该键的分区，
 * 或如果该键不由所有分区列组成，一组分区。对于非相等策略，我们需要根据需要包括其他分区。
 *
 * 'nullkeys' 是一个包含与 IS NULL 子句匹配的分区键的偏移量的集合（0 到
 * partnatts - 1）。这仅在哈希分区中考虑，因为我们需要将哪些键为空传递给哈希分区
 * 边界搜索函数。不可能在给定的分区键中在 'exprs' 中存在一个表达式，并在
 * 'nullkeys' 中设置相应的位。
 */
typedef struct PartitionPruneStepOp
{
	PartitionPruneStep step;

	StrategyNumber opstrategy;
	List	   *exprs;
	List	   *cmpfns;
	Bitmapset  *nullkeys;
} PartitionPruneStepOp;

/*
 * PartitionPruneStepCombine - 使用 BoolExpr 子句进行修剪的信息
 *
 * 对于 BoolExpr 子句，我们结合为每个论据子句确定的分区集合。
 */
typedef enum PartitionPruneCombineOp
{
	PARTPRUNE_COMBINE_UNION,
	PARTPRUNE_COMBINE_INTERSECT
} PartitionPruneCombineOp;

typedef struct PartitionPruneStepCombine
{
	PartitionPruneStep step;

	PartitionPruneCombineOp combineOp;
	List	   *source_stepids;
} PartitionPruneStepCombine;


/*
 * 计划无效信息
 *
 * 我们通过两种方式跟踪 PlannedStmt 依赖的对象：
 * 关系被记录为 OID 的简单列表，其他所有内容
 * 则表示为 PlanInvalItems 的列表。PlanInvalItem 被设计为
 * 与系统缓存无效机制一起使用，因此它通过缓存 ID 和哈希值
 * 标识一个系统目录条目。
 */
typedef struct PlanInvalItem
{
	NodeTag		type;
	int			cacheId;		/* syscache ID，见 utils/syscache.h */
	uint32		hashValue;		/* 对象缓存查找键的哈希值 */
} PlanInvalItem;

/*
 * 单调函数
 *
 * 允许规划器跟踪函数的单调属性。如果一个函数是单调递增的，则后续调用不能产生比上一次调用
 * 更低的值。单调递减函数在后续调用中不能产生更高的值，而同时单调递增和递减的函数
 * 必须在每次调用中返回相同的值。
 */
typedef enum MonotonicFunction
{
	MONOTONICFUNC_NONE = 0,
	MONOTONICFUNC_INCREASING = (1 << 0),
	MONOTONICFUNC_DECREASING = (1 << 1),
	MONOTONICFUNC_BOTH = MONOTONICFUNC_INCREASING | MONOTONICFUNC_DECREASING
} MonotonicFunction;

#endif							/* PLANNODES_H */
